<?php
// $Id: build.inc,v 1.9 2008/10/18 18:25:04 silviogutierrez Exp $

/**
 * @file
 * Provides all the picture rebuilding functions for Gallerix.
 */


/**
 * Renders a rebuilding screen.
 *
 */
function gallerix_rebuild() {
  $output = '';

  //Load common.js.  
  gallerix_load_js();  
    
  //Include rebuilding JavaScript and CSS.
  $path = drupal_get_path('module', 'gallerix');
  drupal_add_js($path . '/js/rebuild.js');
  
  drupal_add_js(array('gallerixRebuild' => array(
    'rebuildURL' => url('admin/gallerix/rebuild/build/'),
    'finalizeURL' => url('admin/gallerix/rebuild/finalize/'),    
    'progressBar' => theme('progress_bar', 0, t('Rebuilding')),
  )), 'setting');
    
  
  $output .= '<p>' . t("By clicking Rebuild, Drupal will go through every picture in the database and perform the selected action. 
                        Note that servers may limit your script execution time. To avoid exceeding this limit, Gallerix uses a threshold and breaks up the operation into batches.")  . '</p>';
  $output .= '<p>' . t('Do not navigate away from this page during a rebuild operation. 
                         Some of these operations are very intensive and can take a very long time. 
                         As long as the progress bar increases ocassionally, everything is working smoothly.') . '</p>';                      
  $output .= drupal_get_form('gallerix_rebuild_form');
  $output .= '<div id="progress-bar-wrapper"></div>';



  return $output;
}




/**
 * Adds a few form elements to the rebuild page.
 *
 */
function gallerix_rebuild_form() {
  $form['threshold'] = array(
    '#type' => 'select',
    '#title' => t('Threshold'),
    '#description' => t("To avoid a script timeout, or a database connection timeout, set the threshold value to something conservative. 
                         The bigger the pictures uploaded, the lower this threshold should be. The threshold is usually dependent on your host's capabilities."),
    '#options' => drupal_map_assoc(array(5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100)),
    '#default_value' => variable_get('gallerix_rebuild_threshold', 10),
                          
  );
  $form['derivatives'] = array(
    '#type' => 'fieldset',
    '#title' => t('Derivatives'),
    '#description' => t('If you have added new sizes, changed resolutions of existing sizes, or changed the image quality to be used, 
                         you should rebuild all derivatives. This process can take a long time and will practically monopolize the server.'),
    '#attributes' => array('id' => 'gallerix-fieldset-derivatives'),                     
  );
  $form['derivatives']['submit'] = array(
    '#type' => 'button',
    '#value' => t('Rebuild'),
    '#id' => 'gallerix-rebuild-derivatives',
    '#attributes' => array('class' => 'gallerix-rebuild'),
  );

  $form['exif'] = array(
    '#type' => 'fieldset',
    '#title' => t('EXIF Data'),
    '#description' => t("You should rebuild EXIF data if you've added new fields to extract. If you have just turned on extraction for the first time, you must rebuild as well."),    
    '#attributes' => array('id' => 'gallerix-fieldset-exif'),    
  );
  $form['exif']['submit'] = array(
    '#type' => 'button',
    '#value' => t('Rebuild'),
    '#id' => 'gallerix-rebuild-exif',
    '#attributes' => array('class' => 'gallerix-rebuild'),    
  );
  
  $form['iptc'] = array(
    '#type' => 'fieldset',
    '#title' => t('IPTC Data'),
    '#description' => t("You should rebuild IPTC data after activating the feature for the first time."),
    '#attributes' => array('id' => 'gallerix-fieldset-iptc'),          
  );
  $form['iptc']['submit'] = array(
    '#type' => 'button',
    '#value' => t('Rebuild'),
    '#id' => 'gallerix-rebuild-iptc',
    '#attributes' => array('class' => 'gallerix-rebuild'),    
  );
  
  $form['sort'] = array(
    '#type' => 'fieldset',
    '#title' => t('Sorting'),
    '#description' => t("Resort albums if you have changed the default order. This will not affect albums set to custom sorting."),      
    '#attributes' => array('id' => 'gallerix-fieldset-sort'),      
  );
  $form['sort']['submit'] = array(
    '#type' => 'button',
    '#value' => t('Rebuild'),
    '#id' => 'gallerix-rebuild-sort',
    '#attributes' => array('class' => 'gallerix-rebuild'),    

  );
  $form['names'] = array(
    '#type' => 'fieldset',
    '#title' => t('Secure/Restore Names'),
    '#description' => t("If you want to remove security, disable secure mode, and this will restore original names to your files. Similarly, if you want to
                         transition to secure mode, enable it, and this will secure file names."),      
    '#attributes' => array('id' => 'gallerix-fieldset-names'),      
  );  
  $form['names']['submit'] = array(
    '#type' => 'button',
    '#value' => t('Rebuild'),
    '#id' => 'gallerix-rebuild-names',
    '#attributes' => array('class' => 'gallerix-rebuild'),    

  );  
  $form['all'] = array(
    '#type' => 'fieldset',
    '#title' => t('Everything'),
    '#description' => t("Rebuild everything in the catalog. This does not include restoring secure names or original names."),      
    '#attributes' => array('id' => 'gallerix-fieldset-all'),      
  );  
  $form['all']['submit'] = array(
    '#type' => 'button',
    '#value' => t('Rebuild All'),
    '#id' => 'gallerix-rebuild-all',
    '#attributes' => array('class' => 'gallerix-rebuild'),    
  );        
    
  
  return $form;
}



function gallerix_build_restore_name($pid) {

  $secure = variable_get('gallerix_secure_mode', 0);
  
  $picture = gallerix_load_picture($pid);


  $sizes = gallerix_sizes();
  if ($secure) {
    foreach($sizes as $size => $properties) {
      _gallerix_secure_file($picture->$size);
    }   
    $secure_path = _gallerix_secure_file($picture->original);
    $secure_name = basename($secure_path);
    
    db_query("UPDATE {gallerix_pictures} SET name = '%s', path = '%s' WHERE pid = %d", $secure_name, $secure_path, $pid);
      
  }
  else {
  
    //Add in the dummy original size.
    $sizes['original'] = TRUE;
  
    foreach($sizes as $size => $properties) {
      
      $directory = dirname($picture->$size);
      $renamed_path = $directory . '/' . $picture->original_name;
      rename($picture->$size, $renamed_path);
      
      
      $picture->$size = $renamed_path; 
    }
    
    db_query("UPDATE {gallerix_pictures} SET name = '%s', path = '%s' WHERE pid = %d", $picture->original_name, $picture->original, $pid);
        
  }


}


/**
 * AJAX call that rebuilds a number of pictures. 
 *
 * @param pid specifies where to start rebuilding.
 *
 */
function gallerix_rebuild_build($pid, $threshold = 10) {
  
  //Load the requested operations.
  $operations = explode('|#|', $_POST['gallerix_operations']);
  
  $pictures = db_query_range('SELECT pid FROM {gallerix_pictures} WHERE pid > %d ORDER BY pid', $pid, 0, $threshold);  
  $remaining = db_affected_rows();
  
  
  if (!$remaining) {
    $response = array(
      'percentage' => 100,
      'status' => 'done',
      'current' => -1,
    );  
  }
  else {
    $completed  = db_result(db_query('SELECT COUNT(pid) FROM {gallerix_pictures} WHERE pid < %d', $pid));    
    $total = db_result(db_query('SELECT COUNT(pid) FROM {gallerix_pictures}'));
    
    
    $count = 0;
    
    while ($pid = db_fetch_object($pictures)) {
      $current = $pid->pid;
      
      //Securing names is a special operation.
      if ($operations[0] == 'names') {
        gallerix_build_restore_name($current);
      }
      else {
        gallerix_build_picture($current, $operations);
      }
      $count++;
    }
    
    //Factor in the pictures we just processed.
    $completed += $count;
    
    $percentage = ceil(($completed/$total) * 100);
    $percentage = (int) $percentage;    
    
    $response = array(
      'completed' => $completed,
      'total' => $total,
      'percentage' => $percentage,    
      'status' => 'continue',    
      'current' => $current,
    );
  
  }
  
    
  print gallerix_to_js($response);


  exit();
}



/**
 * AJAX call that finalizes the rebuild operation by saving the threshold
 * setting, and clearing the Gallerix cache.
 *
 * @param pid specifies where to start rebuilding.
 *
 */
function gallerix_rebuild_finalize($threshold) {
  if ($threshold > 0) {
    variable_set('gallerix_rebuild_threshold', $threshold);
  }
  cache_clear_all(NULL, 'cache_gallerix');
}







/************************* Album Rebuilding ***************************/

/**
 * Displays the album building page.
 *
 */
function gallerix_build_album($node) {
  $output = '';

  drupal_set_title(t('Building Album'));

  //Load common.js.  
  gallerix_load_js();  
    
  //Include rebuilding JavaScript and CSS.
  $path = drupal_get_path('module', 'gallerix');
  drupal_add_js($path . '/js/build.js');
  
  drupal_add_js(array('gallerixBuild' => array(
    'buildURL' => url('node/' . $node->nid . '/pictures/build/picture/'),
    'finalizeURL' => url('node/' . $node->nid . '/pictures/build/finalize'),    
  )), 'setting');

  $output .= theme('progress_bar', 0, t('Building'));
  $output .= drupal_get_form('gallerix_build_album_form');  
  
  return $output;
}



/**
 * Add an empty form for a redirect.
 *
 */
function gallerix_build_album_form() {
  $nid = arg(1);

  $form = array(
    '#action' => url('node/' . arg(1) . '/pictures/build/finalize'),
  );
    
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Rebuild'),
    '#id' => 'gallerix-rebuild',
    '#attributes' => array('style' => 'display: none;'),
  );  

  return $form;
}


/**
 * Resort the album and redirect to the manage pictures section.
 *
 */
function gallerix_build_album_form_submit($form_id, &$form_state) {
  $nid = arg(1);
  
  $node = node_load($nid);

  gallerix_sort_album($node);
      
  $form_state['redirect'] =  'node/' . $nid . '/pictures/manage';
}



/**
 * Builds pictures that have a PID greater than the one provided.
 * Threshold limits how many pictures to build during each call.
 *
 */
function gallerix_build_album_picture($pid, $completed) {

  $threshold = variable_get('gallerix_rebuild_threshold', 10);
  $built = variable_get('gallerix_rebuild', 0);
  $nid = arg(1);
  
  
  $pictures = db_query_range('SELECT pid FROM {gallerix_pictures} WHERE pid > %d AND built <= %d AND nid = %d ORDER BY pid', $pid, $built, $nid, 0, $threshold);  
  $remaining = db_affected_rows();
  
  
  if (!$remaining) {
    $response = array(
      'percentage' => 100,
      'status' => 'done',
      'current' => -1,
    );  
  }
  else {   

    $remaining = db_result(db_query('SELECT COUNT(pid) FROM {gallerix_pictures} WHERE nid = %d AND built <= %d', $nid, 0));
    $count = 0;
    
    while ($pid = db_fetch_object($pictures)) {
      //DO STUFF HERE.
      $current = $pid->pid;
      
      gallerix_build_picture($current);
      $count++;
    }
    

    $total = $completed + $remaining;    
    
    //Factor in the pictures we just processed.
    $completed += $count;
    
    $percentage = ceil(($completed/$total) * 100);
    $percentage = (int) $percentage;    
    
    $response = array(
      'completed' => $completed,
      'total' => $total,
      'remaining' => $remaining,
      'percentage' => $percentage,    
      'status' => 'continue',    
      'current' => $current,
    );
  
  }
  
    
  print gallerix_to_js($response);


  exit();
}



/********************* Public Rebuilding Functions ********************/



/**
 * Rebuilds a picture, including derivatives, IPTC and EXIF data.
 *
 * @param pid the picture to rebuild.
 *
 * @param operations an array of all the actions to perform. Options include
 * 'iptc', 'exif' and 'derivatives'. If no array is passed, all actions will be performed.
 *
 * @param source indicates what picture file to use as the source. Most of the time this
 * will be left as 'original'.
 *
 */
function gallerix_build_picture($pid, $operations = array('derivatives', 'iptc', 'exif', 'sort'), $source = 'original') {
  
  $picture = gallerix_load_picture($pid);
  
  // Build derivatives.
  if (in_array('derivatives', $operations)) {
    $sizes = gallerix_sizes();
    
    $album_path = file_directory_path() . '/gallerix/albums/' . $picture->uid . '/' . $picture->nid;
  
    // Delete old derivatives.
    $directory = dir($album_path);
  
    while (FALSE !== ($entry = $directory->read())) {
      if ($entry == '.' || $entry == '..' || isset($sizes[$entry]) || $entry == 'original') {
        continue;
      }
      gallerix_delete_directory($album_path . '/' . $entry);
    }

    $directory->close();
    
    foreach ($sizes as $size => $properties) {
    
      // Don't rebuild the source.
      if ($size == $source) {
        continue;
      }
     
      file_check_directory(dirname($picture->$size), FILE_CREATE_DIRECTORY);
      
      // Check if resizing is necessary.
      $info = image_get_info($picture->$source);
      
      if ($properties['width'] >= $info['width'] && $properties['height'] >= $info['height']) {
        $success = file_copy($picture->$source, $picture->$size, FILE_EXISTS_REPLACE);
      }
      elseif ($properties['ratio']) {
        $success = gallerix_ratio_resize($picture->$source, $picture->$size, $properties['width'], $properties['height']);
      }
      else {
        $success = image_scale($picture->$source, $picture->$size, $properties['width'], $properties['height']); 
        db_query("UPDATE {gallerix_pictures} SET built = %d WHERE pid = %d", time(), $pid);    
        
      }
      
      if (!$success) {
        watchdog('gallerix', 'Error rebuilding !picture into size "!size"', array('!picture' => $picture->pid, '!size' => $size), WATCHDOG_ERROR);
      }
      else {
        chmod($picture->$size, 0777);    
      }
    
    }
  }
  
  
  
  $caption = '';
  
  //Extract EXIF data.
  if (in_array('exif', $operations) && variable_get('gallerix_extract_exif', 0)) {
    $exif = gallerix_extract_exif($pid);
    
    
    //Caption handling. Can be overriden by an IPTC caption below.
    if (isset($exif['ImageDescription']) && in_array('iptc', $operations)) {
      $caption = $exif['ImageDescription'];
    }
    
    //If the picture contains the time taken, update the database to reflect this time.
    if (isset($exif['EXIF']['DateTimeOriginal'])) {
      
      $created = _gallerix_exif_date($exif['EXIF']['DateTimeOriginal']);
      $exif = serialize($exif);
      
      db_query("UPDATE {gallerix_pictures} SET exif = '%s', created = %d WHERE pid = %d", $exif, $created, $pid);     
    }
    else {
    
      $exif = serialize($exif);
      db_query("UPDATE {gallerix_pictures} SET exif = '%s' WHERE pid = %d", $exif, $pid);     
    }
    

    
  }  
  
  
  //Extract IPTC data.
  if (in_array('iptc', $operations) && variable_get('gallerix_extract_iptc', 0)) {
    $tags = gallerix_extract_iptc($pid);
    
    //Caption handling. 
    if (isset($tags['2#120'])) { //First try the standard 2#120 caption tag.
      $caption = $tags['2#120'][0];
    }
    elseif (isset($tags['2#005'])) { //Then try the less standard 2#005 tag.
      $caption = $tags['2#005'][0];
    }
    
    $tags = serialize($tags);
    
    db_query("UPDATE {gallerix_pictures} SET iptc = '%s' WHERE pid = %d", $tags, $pid);    
  }
  
  
  $caption = check_plain($caption);
  $caption = trim($caption);
  
  //Update the caption if necessary.
  if (!$picture->caption && $caption) {

    
    db_query("UPDATE {gallerix_pictures} SET caption = '%s' WHERE pid = %d", $caption, $pid);
  
  }
  
  
  //Only sort if it's been requested AND this is the last picture in the album.
  if (in_array('sort', $operations) && !db_result(db_query("SELECT pid FROM {gallerix_pictures} WHERE nid = %d AND pid > %d", $picture->nid, $picture->pid))) {
    $node = node_load($picture->nid);
    gallerix_sort_album($node);
  
  
  }
  
  //Clear messages.
  drupal_get_messages('status');
    
  
  return $success;

}






/**
 * Maintains source ratio of the original picture when resizing. 
 * It then crops it into the correct width and height. Although some content
 * might be cut out, this system has aesthetical advantages.
 * By default, thumbnails use this system.
 *
 * @param source indicates where to find the picture.
 * 
 * @param destination indicates where to save the picture.
 * 
 * @param width the exact horizontal length of the derivative.
 * 
 * @param height the exact vertical length of the derivative.
 * 
 * @param x the offset to use horizontally when cropping.
 * 
 * @param y the offset to use vertically when cropping.
 * 
 */
function gallerix_ratio_resize($source, $destination, $width, $height, $x = 0, $y = 0) {

  //Ratios code from ImageCache.
  $image_size = getimagesize($source);
  $ratio = $image_size[0]/$image_size[1];
  $new_ratio = $width/$height;
  $scaleWidth = $ratio > $new_ratio ? 9999999 : $width;
  $scaleHeight = $ratio < $new_ratio ? 9999999 : $height;

  $success = false;
  $success = image_scale($source, $destination, $scaleWidth, $scaleHeight);
  $success = image_crop($destination, $destination, $x, $y, $width, $height);  


  return $success;
}







/**
 * Rotate an image and rebuild all derivatives. Note that the actual original
 * image is not rotated, instead, the Frame derivative is used as the source.
 * 
 * @param pid the picture to be rotated.
 *
 * @param angle the amount to rotate the picture.
 *
 * @return true if rotation was successful. False on failure.
 *
 */
function gallerix_rotate_picture($pid, $angle) {
  $picture = gallerix_load_picture($pid);
  
  
  $success = image_rotate($picture->frame, $picture->frame, $angle);
  $success = gallerix_build_picture($picture->pid, array('derivatives'), 'frame');

  return $success;

}




/*********************** EXIF & IPTC Functions ************************/



/**
 * Extract EXIF data from a picture.
 *
 */
function gallerix_extract_exif($pid) {
  $picture = gallerix_load_picture($pid);
  
  $unprocessed = @exif_read_data($picture->original, '', TRUE);
  $exif = array();
    
  //Can't parse marker notes.  
  unset($unprocessed['EXIF']['MakerNote']);
    
  //Remove undefined tags from each section.
  foreach($unprocessed as $section => $section_data) {
       
    foreach($section_data as $key => $data) {
      
      //Only include defined tags.
      if (!preg_match('/^UndefinedTag.*/', $key)) {
        $exif[$section][$key] = utf8_encode($data);
      }  
    
    }
  
  }
  
  return $exif;
  
} 




/**
 * Extract IPTC data from a picture.
 *
 */
function gallerix_extract_iptc($pid) {
  $picture = gallerix_load_picture($pid);

  $size = getimagesize($picture->original, $data);
  $tags = array();
  
  if(is_array($data)) {   
    
    $iptc = iptcparse($data["APP13"]);
    
    if (is_array($iptc)) {
      foreach ($iptc as $key => $type) {
        
        foreach ($type as $tag) {
          $tags[$key][] = utf8_encode($tag);         
        }
      
      }
    }
                       
  }
  
  //We never want the 2#000 tag, which does not contain useful information.
  unset($tags['2#000']);  

  return $tags;             
} 



/**
 * Returns a UNIX timestamp from EXIF dates.
 *
 * EXIF dates are in a YYYY:MM:DD HH:MM:SS format, which throws off strtotime().
 * We must replace the first half using a PHP4 safe approach. Although PHP5 has a count parameter for str_replace(),
 * we can't use it in PHP4.
 */
function _gallerix_exif_date($date) {

  $date = split(" ", $date);
  $first_half = $date[0];
  $second_half = $date[1];
  
  $first_half = str_replace(":", "-", $first_half);
  $date = $first_half . ' ' . $second_half;
  
  return strtotime($date);
  
}




